//{===============================================================================================================
//! @file    CPUcpp
//! @date    2014-11-02 03:17
//! @author  Vova Gurinovich  <whistlevg@gmail.com>
//!
//!
//!
//! @par     About : This program creates CPU
//!                  and allows you to work with it
//!
//}===============================================================================================================

//!----------------------------------------------------------------------------------------------------------------
//! Libraries and headers
//!----------------------------------------------------------------------------------------------------------------
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <assert.h>
#include <string.h>
#include <math.h>
#include "stack.h"

//!----------------------------------------------------------------------------------------------------------------
//! Define, which dumps, when CPU is not OK.
//!----------------------------------------------------------------------------------------------------------------
#define CHECK_CPU if(CPU_OK(CPU)) { CPU_dump(CPU); }

//!================================================================================================================

enum CPU_Commands_t
{
    #define DEF_CMD(name , number , code , reg) name = number,
    #include "commands.h"
    #undef DEF_CMD(name , number , code , reg)
    FINISH
};
CPU_Commands_t CPU_COMMAND;


enum CPU_errors {WRONG_POINTER , OK_CPU , WRONG_REGISTER_NUMBER};
CPU_errors CPU_ERROR = OK_CPU;

enum type_function {JA , JE , JB , JAE , JBE , JNE , JUMP};
type_function TYPE_OF_JUMP;

//!=================================================================================================================

struct CPU_t
{
    double* Regs;
    int Reg_number;

    double* commands;

    stack_t Stack_CALL;
    stack_t Stack_CPU;
};


//!====================================================================================================

    void CPU_help();

//!----------------------------------------------------------------------------------------------------
//! Function handles commands from an array of commands
//!----------------------------------------------------------------------------------------------------
    void performer(CPU_t* CPU);

//!----------------------------------------------------------------------------------------------------
//! Construct & destruct functions
//!----------------------------------------------------------------------------------------------------
    void CPU_Construct(CPU_t* CPU);
    void Fill_Regs(CPU_t* CPU);
    void CPU_Destruct(CPU_t* CPU);

    void FillCommands(CPU_t* CPU , FILE* code_f);

//!----------------------------------------------------------------------------------------------------
//! Sets the register is throwing argument
//!----------------------------------------------------------------------------------------------------
    void CPU_pop_reg(CPU_t* CPU , int* step);

//!----------------------------------------------------------------------------------------------------
//! Pushes the value of the register
//!----------------------------------------------------------------------------------------------------
    void CPU_PUSHAR_REG(CPU_t* CPU , int* step);

//!----------------------------------------------------------------------------------------------------
//! Generic function with a parameter of the jump
//!----------------------------------------------------------------------------------------------------
    void CPU_JUMP_PARAM(CPU_t* CPU , int* step , type_function TYPE_OF_JUMP);

    void CPU_OUT(CPU_t* CPU , int* step);

//!----------------------------------------------------------------------------------------------------
//! Printing last value in main stack
//!----------------------------------------------------------------------------------------------------
    void CPU_OUT_LAST_VALUE(CPU_t* CPU , int* step);

//!----------------------------------------------------------------------------------------------------
//! CPU check functions
//!----------------------------------------------------------------------------------------------------
    int CPU_OK(CPU_t* CPU);
    void CPU_dump(CPU_t* CPU);

//!----------------------------------------------------------------------------------------------------
//! Functions working with errors
//!----------------------------------------------------------------------------------------------------
    char* CPU_strerror(CPU_errors CPU_ERROR);
    void CPU_printERROR(char* str);

//!----------------------------------------------------------------------------------------------------
//! Call functions
//!----------------------------------------------------------------------------------------------------
    void CPU_call(CPU_t* CPU , int* step);
    void CPU_RET(CPU_t* CPU , int* step);

//!----------------------------------------------------------------------------------------------------
//! Basic math operations, but can add any math operations with 2 param
//!----------------------------------------------------------------------------------------------------
    double divide_f(double a , double b);
    double diff_f(double a , double b);
    double multiplication_f(double a , double b);
    double sum(double a , double b);
//!----------------------------------------------------------------------------------------------------
//! Operations with 1\2 arguments
//!----------------------------------------------------------------------------------------------------
    void Unary_ops  (CPU_t* CPU , double (*math_func)(double) , int* step);
    void Binary_ops (CPU_t* CPU , double (*math_func)(double , double) , int* step);

    void REGISTER_INPUT(CPU_t* CPU , int* step);

//!====================================================================================================

const int CPU_STACK_ELEMENTS_NUMBER = 300;
const int CPU_MAX_NUMBER_COMMANDS   = 300;
const int CPU_CALL_FUNC_NUMBER      = 50;
const int MAX_REGISTER_NUMBER       = 20;
const int START_REG_VALUE           = -1;

//!====================================================================================================

int main()
{
    system("ASM_N.exe");

    CPU_t CPU = {};
    CPU_Construct(&CPU);

    FILE* code_f = fopen("code.txt" , "r");
    assert(code_f);

    FillCommands(&CPU , code_f);

    CPU_help();

    performer(&CPU);

    return 0;

}

//!====================================================================================================
//!====================================================================================================

void FillCommands(CPU_t* CPU , FILE* code_f)
{
    assert(code_f);
    CHECK_CPU

    double command = 0;

    int step = 0;

    while(command != EXIT)
    {

        int OK = fscanf(code_f , "%lg" , &command);
        assert(OK);

        (*CPU).commands[step] = command;
        step++;

    }

    CHECK_CPU

}

//!====================================================================================================
void performer(CPU_t* CPU)
{

    CHECK_CPU
    int step = 0;

    for (;;)
    {
        double command = (*CPU).commands[step++];

        if(command == EXIT) break;

        #define GET_ARG (*CPU).commands[step++]
        #define DEF_CMD(name , number , code , reg) if(command == name) code ;
        #include "commands.h"
        #undef DEF_CMD(name , number , code , reg)

        CHECK_CPU

    }

    CHECK_CPU

}
//!====================================================================================================

void Unary_ops (CPU_t* CPU , double (*math_func)(double) , int* step)
{
    assert(step);
    CHECK_CPU

    double result = math_func(Stack_pop(&(CPU->Stack_CPU)));
    Stack_Push(&(CPU->Stack_CPU) , result);

    CHECK_CPU
}

void Binary_ops (CPU_t* CPU , double (*math_func)(double , double) , int* step)
{
     assert(step);
     CHECK_CPU

     double result = math_func(Stack_pop(&(CPU->Stack_CPU)) , Stack_pop(&(CPU->Stack_CPU)));
     Stack_Push(&(CPU->Stack_CPU) , result);

     CHECK_CPU
}

//!=====================================================================================================

void CPU_pop_reg(CPU_t* CPU , int* step)
{
    assert(step);
    CHECK_CPU

    CPU->Reg_number = (*CPU).commands[(*step)];
    (*step)++;

    CPU->Regs[CPU->Reg_number] = Stack_pop(  &((*CPU).Stack_CPU) );

    CHECK_CPU
}

void CPU_PUSHAR_REG(CPU_t* CPU , int* step)
{
    assert(step);
    CHECK_CPU

    CPU->Reg_number = (*CPU).commands[(*step)];
    (*step)++;

    Stack_Push( &(CPU->Stack_CPU) , (CPU->Regs[CPU->Reg_number]));

    CHECK_CPU
}

//!=====================================================================================================

void CPU_OUT(CPU_t* CPU , int* step)
{
    assert(step);
    CHECK_CPU

    CPU->Reg_number = (*CPU).commands[(*step)];
    (*step)++;

    printf("\n register number %d = %lg" , CPU->Reg_number , CPU->Regs[CPU->Reg_number]);

    CHECK_CPU
}

//!======================================================================================================
//!======================================================================================================

void CPU_call(CPU_t* CPU , int* step)
{
    assert(step);
    CHECK_CPU

    Stack_Push( &(CPU->Stack_CALL) , (*step) + 1 );
    *step = (*CPU).commands[(*step)];

    CHECK_CPU
}

void CPU_RET(CPU_t* CPU , int* step)
{
    assert(step);
    CHECK_CPU

    if( (*CPU).Stack_CALL.counter > 0)
    {

    *step = Stack_pop(&(CPU->Stack_CALL));

    }

    CHECK_CPU
}
//!======================================================================================================
//!======================================================================================================

void CPU_JUMP_PARAM(CPU_t* CPU , int* step , type_function TYPE_OF_JUMP)
{
    assert(step);
    CHECK_CPU

    double a = (*CPU).Stack_CPU.elements[(*CPU).Stack_CPU.counter - 1];
    double b = (*CPU).Stack_CPU.elements[(*CPU).Stack_CPU.counter - 2];

    switch(TYPE_OF_JUMP)
    {
        case JUMP  : {*step = (*CPU).commands[(*step)];}                                    break;

        case JA   :  if(a >  b)  { *step = (*CPU).commands[(*step)];   } else {(*step)++;}  break;

        case JE   :  if(a == b)  {  *step = (*CPU).commands[(*step)];  } else {(*step)++;}  break;

        case JNE  :  if(a != b)  {  *step = (*CPU).commands[(*step)];  } else {(*step)++;}  break;

        case JAE  :  if(a >= b)  {  *step = (*CPU).commands[(*step)];  } else {(*step)++;}  break;

        case JBE  :  if(a <= b)  {  *step = (*CPU).commands[(*step)];  } else {(*step)++;}  break;

        case JB   :  if(a <  b)  { *step = (*CPU).commands[(*step)];   } else {(*step)++;}  break;

        default : break;
    }


    CHECK_CPU
}


//!====================================================================================================
void CPU_Construct(CPU_t* CPU)
{
    CPU->commands = (double*)calloc(CPU_MAX_NUMBER_COMMANDS , sizeof(double));
    CPU->Regs = (double*)calloc(MAX_REGISTER_NUMBER , sizeof(double));

    Fill_Regs(CPU);

    CPU->Reg_number = 0;

    Stack_Construct( &( (*CPU).Stack_CPU ) , CPU_STACK_ELEMENTS_NUMBER);
    Stack_Construct( &( (*CPU).Stack_CALL) , CPU_CALL_FUNC_NUMBER);

    CHECK_CPU
}

void Fill_Regs(CPU_t* CPU)
{
    for(int i = 0; i < MAX_REGISTER_NUMBER; i++)
    {
        CPU->Regs[i] = START_REG_VALUE;
    }
}

void CPU_Destruct(CPU_t* CPU)
{
    CHECK_CPU

    free(CPU->commands);
    (CPU->commands) = NULL;

    free(CPU->Regs);
    (CPU->Regs) = NULL;

    Stack_destruct( &(CPU->Stack_CPU));
    Stack_destruct( &(CPU->Stack_CALL));
}
//!====================================================================================================

int CPU_OK(CPU_t* CPU)
{

    if( Stack_OK(&(CPU->Stack_CPU))    ) {return 1;}
    if( Stack_OK(&(CPU->Stack_CALL))   ) {return 1;}

    if(CPU == NULL) {CPU_ERROR = WRONG_POINTER; return 1;}

    if(CPU->Reg_number >= MAX_REGISTER_NUMBER || CPU->Reg_number < 0) {CPU_ERROR = WRONG_REGISTER_NUMBER; return 1;}

    return 0;

}

//!====================================================================================================

void CPU_dump(CPU_t* CPU)
{
    CHECK_CPU

    if(Stack_OK(&(CPU->Stack_CPU)))   {printf ("ERROR in CPU main Stack");  Stack_dump( &(CPU->Stack_CPU) , 1); }
    if(Stack_OK(&(CPU->Stack_CALL)))  {printf ("ERROR in CPU Stack CALL");  Stack_dump( &(CPU->Stack_CALL) , 1);}

    CPU_printERROR("WTF:");

}

//!====================================================================================================
//!====================================================================================================

char* CPU_strerror(CPU_errors CPU_ERROR)
{
    switch(CPU_ERROR)
    {

    case WRONG_POINTER:                    return  "Wrong pointer to CPU struct\n";                              break;
    case WRONG_REGISTER_NUMBER:            return  "Wrong register number , check MAX_REGISTER_NUMBER constant"; break;
    case OK_CPU:                           return  "Strange if you see this with OK CPU";                        break;
    default:                               return  "ERROR";                                                      break;

    }

}

void CPU_printERROR(char* str)
{
    assert(str);
    char* error_cpu = CPU_strerror(CPU_ERROR);
    printf("%s%s" , str , error_cpu);
}

//!====================================================================================================
//!====================================================================================================

double divide_f(double a , double b)
{
    return b/a;
}

double diff_f(double a , double b)
{
    return b - a;
}

double multiplication_f(double a , double b)
{
    return a*b;
}

double sum(double a , double b)
{
    return a + b;
}

//!==========================================================================================

void REGISTER_INPUT(CPU_t* CPU , int* step)
{
    CHECK_CPU
    assert(step);

    CPU->Reg_number = (*CPU).commands[(*step)];
    (*step)++;

    double value = 0;

    int OK = scanf("%lg" , &value); assert(OK);
    (CPU->Regs[CPU->Reg_number]) = value;

}

//!==========================================================================================

void CPU_OUT_LAST_VALUE(CPU_t* CPU , int* step)
{
    CHECK_CPU
    assert(step);
    printf("\n out_last:%lg\n" , (*CPU).Stack_CPU.elements[(*CPU).Stack_CPU.counter - 1]);
}

//!==========================================================================================

void CPU_help()
{
    printf("\n============================================================\n");
    printf("This is CPU processor \n");
    printf("============================================================\n");
}

